package red.stu.pconstraint.scanner;

import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 资源扫描器
 * @author yihrmc @stu.red
 * @date 2019/11/18 11:43
 */
public abstract class ResourceScanner<T> {

    private static final String PROTOCOL_FILE = "file";

    private static final String PROTOCOL_JAR = "jar";

    public static final char pathSeparatorChar = '/';

    public static final String pathSeparator = pathSeparatorChar + "";

    /**
     * 开始扫描
     * @param scanPath 资源路径
     * @return
     */
    protected List<T> scan(@NotNull String scanPath) {
        if (scanPath.indexOf("\\") != -1) {
            scanPath = scanPath.replaceAll("\\\\", pathSeparator);
        }
        if (scanPath.startsWith(pathSeparator)) {
            scanPath = scanPath.substring(1);
        }
        if (scanPath.endsWith(pathSeparator)) {
            scanPath = scanPath.substring(0, scanPath.length() - 1);
        }
        LinkedList<T> resourceList = new LinkedList<>();
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> resources;
        try {
            resources = loader.getResources(scanPath);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        while (resources.hasMoreElements()) {
            URL url = resources.nextElement();
            String protocol = url.getProtocol();
            if (PROTOCOL_JAR.equalsIgnoreCase(protocol)) {
                List<T> jarClassNameList = scanJar(url, scanPath);
                resourceList.addAll(jarClassNameList);

            } else if (PROTOCOL_FILE.equalsIgnoreCase(protocol)) {
                List<T> fileClassNameList = scanFile(url, scanPath);
                resourceList.addAll(fileClassNameList);

            }
        }
        return resourceList;
    }

    /**
     * 扫描jar资源
     * @param jarUrl
     * @param scanPath
     * @return
     */
    protected List<T> scanJar(URL jarUrl, String scanPath) {
        try {
            JarURLConnection jarURLConnection = (JarURLConnection) jarUrl.openConnection();
            JarFile file = jarURLConnection.getJarFile();
            LinkedList<T> resourceList = new LinkedList<>();
            Enumeration<JarEntry> entries = file.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                String filePath = jarEntry.getName();
                if (filePath.startsWith(scanPath)) {
                    T resource = handleScanFile(filePath);
                    if (resource != null) {
                        resourceList.add(resource);
                    }
                }
            }
            return resourceList;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 扫描系统文件
     * @param url
     * @param baseScanPath
     * @return
     */
    protected List<T> scanFile(URL url, String baseScanPath) {
        LinkedList<T> resourceList = new LinkedList<>();
        LinkedList<ScanPath> scanPathList = new LinkedList<>();
        scanPathList.add(new ScanPath(url.getPath(), baseScanPath));
        while (scanPathList.size() > 0) {
            ScanPath scanPath = scanPathList.removeFirst();
            // scan File
            File pathFile = new File(scanPath.path);
            File[] files = pathFile.listFiles();
            if (files != null && files.length > 0) {
                for (int i = 0, len = files.length; i < len; i++) {
                    File file = files[i];
                    if (file.isDirectory()) {
                        // is Directory
                        String name = file.getName();
                        String nextPath = file.getPath();
                        String absolutePath = scanPath.absolutePath + pathSeparator + name;
                        scanPathList.add(new ScanPath(nextPath, absolutePath));

                    } else {
                        // is File
                        String absolutePath = scanPath.absolutePath + pathSeparator + file.getName();
                        T resource = handleScanFile(absolutePath);
                        if (resource != null) {
                            resourceList.add(resource);
                        }

                    }
                }
            } else if (pathFile.isFile()) {
                String absolutePath = scanPath.absolutePath;
                T resource = handleScanFile(absolutePath);
                if (resource != null) {
                    resourceList.add(resource);
                }
            }
        }
        return resourceList;
    }

    /**
     * 处理扫描到的资源
     * @param filePath 扫描到的资源相对路径
     * @return null放弃资源，否则加入资源
     */
    protected abstract T handleScanFile(String filePath);

    private class ScanPath {
        public String path;

        public String absolutePath;

        public ScanPath(String path, String absolutePath) {
            this.path = path;
            this.absolutePath = absolutePath;
        }
    }

}
